package org.fjsei.yewu.jpa;
//protected 方法没法直接修改，所以照抄了 扩展！ 原始包package com.querydsl.core.types;

/*
 * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//package com.querydsl.core.types;

import com.querydsl.core.group.GroupExpression;
import com.querydsl.core.types.*;
import com.querydsl.core.util.PrimitiveUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * {@code QBean} is a JavaBean populating projection type
 *
 * <p>Example</p>
 *
 * <pre>
 * {@code
 * QEmployee employee = QEmployee.employee;
 * List<EmployeeInfo> result = query.from(employee)
 *      .where(employee.valid.eq(true))
 *      .select(Projections.bean(EmployeeInfo.class, employee.firstName, employee.lastName))
 *      .fetch();
 * }
 * </pre>
 *
 * @author tiwe
 *
 * @param <T> bean type
 * 目的简化掉sql查询的没用字段，加快查询；
 * 目前支持是2层的嵌套投影：查询出来的实例，可以用于JPA其它操作，也能够用于VO发给前端的。
 * 约束：同一个关联对象一起顺序编列，可支持最多两层的interface投影：免于1+N直接join Fetch然后构造嵌套的实体。
 */
public class QBeanMy<T> extends QBean<T> {

    private static final long serialVersionUID = -8210214512730989778L;
    /**第二层嵌套关联对象的set()构造器；
     * */
    private  Method[]  sonSetters;
    /**嵌套关联对象的Qbean,为了能把cross join改成left outer join才引入的
     * */
    private  List<EntityPath<?>>  leftJoinFields;


    private static Map<String, Expression<?>> createBindings(Expression<?>... args) {
        Map<String, Expression<?>> rv = new LinkedHashMap<>();
        for (Expression<?> expr : args) {
            if (expr instanceof Path<?>) {
                Path<?> path = (Path<?>) expr;
                String key=path.toString();
                key= key.substring(path.getRoot().toString().length()+1);
                //String key=path.getMetadata().getName(); 原始版本用的：只能是叶子节点的名字
                rv.put(key, expr);
            } else if (expr instanceof Operation<?>) {
                Operation<?> operation = (Operation<?>) expr;
                if (operation.getOperator() == Ops.ALIAS && operation.getArg(1) instanceof Path<?>) {
                    Path<?> path = (Path<?>) operation.getArg(1);
                    if (isCompoundExpression(operation.getArg(0))) {
                        rv.put(path.getMetadata().getName(), operation.getArg(0));
                    } else {
                        rv.put(path.getMetadata().getName(), operation);
                    }
                } else {
                    throw new IllegalArgumentException("Unsupported expression " + expr);
                }

            } else {
                throw new IllegalArgumentException("Unsupported expression " + expr);
            }
        }
        return Collections.unmodifiableMap(rv);
    }

    private static boolean isCompoundExpression(Expression<?> expr) {
        return expr instanceof FactoryExpression || expr instanceof GroupExpression;
    }

    private static Class<?> normalize(Class<?> cl) {
        return cl.isPrimitive() ? PrimitiveUtils.wrap(cl) : cl;
    }

    private static boolean isAssignableFrom(Class<?> cl1, Class<?> cl2) {
        return normalize(cl1).isAssignableFrom(normalize(cl2));
    }

    private final Map<String, Expression<?>> bindings;

    private final List<Field> fields;

    private final List<Method> setters;

    private final boolean fieldAccess;

    /**
     * Create a new QBean instance
     *
     * @param type type of bean
     * @param bindings bindings
     */
    protected QBeanMy(Class<? extends T> type, Map<String, ? extends Expression<?>> bindings) {
        this(type, false, bindings);
    }

    /**
     * Create a new QBean instance
     *
     * @param type type of bean
     * @param args properties to be populated
     */
    public QBeanMy(Class<? extends T> type, Expression<?>... args) {
        this(type, false, args);
    }

    /**
     * Create a new QBean instance
     *
     * @param type type of bean
     * @param fieldAccess true, for field access and false, for property access
     * @param args fields or properties to be populated
     */
    public QBeanMy(Class<? extends T> type, boolean fieldAccess, Expression<?>... args) {
        this(type, fieldAccess, createBindings(args));
    }

    /**
     * Create a new QBean instance
     *
     * @param type type of bean
     * @param fieldAccess true, for field access and false, for property access
     * @param bindings bindings  # 改成public了
     */
    public QBeanMy(Class<? extends T> type, boolean fieldAccess, Map<String, ? extends Expression<?>> bindings) {
        super(type);
        this.bindings = Collections.unmodifiableMap(new LinkedHashMap<>(bindings));
        this.fieldAccess = fieldAccess;
        if (fieldAccess) {
            this.fields = initFields(bindings);
            this.setters = Collections.emptyList();
        } else {
            this.fields = Collections.emptyList();
            this.sonSetters = new Method[bindings.size()];
            this.setters = initMethods(bindings);
        }
    }

    private List<Field> initFields(Map<String, ? extends Expression<?>> args) {
        List<Field> fields = new ArrayList<Field>(args.size());
        for (Map.Entry<String,? extends Expression<?>> entry : args.entrySet()) {
            String property = entry.getKey();
            Expression<?> expr = entry.getValue();
            Class<?> beanType = getType();
            Field field = null;
            while (!beanType.equals(Object.class)) {
                try {
                    field = beanType.getDeclaredField(property);
                    field.setAccessible(true);
                    if (!isAssignableFrom(field.getType(), expr.getType())) {
                        typeMismatch(field.getType(), expr);
                    }
                    beanType = Object.class;
                } catch (SecurityException e) {
                    // do nothing
                } catch (NoSuchFieldException e) {
                    beanType = beanType.getSuperclass();
                }
            }
            if (field == null) {
                propertyNotFound(expr, property);
            }
            fields.add(field);
        }
        return fields;
    }

/**嵌套对象的设置 sonSetters 按顺序，第一个关联对象属性的位置就是嵌套对象构造器setter的位置。
args.key: 对于有二层对象的是"id", ,,"company.id","company.name"这样的。同一个对象字段顺序输入。第一层字段必须放在最前面。第一层最少一个字段。
* */
    private List<Method> initMethods(Map<String, ? extends Expression<?>> args) {
        String preLinkBase="";   //第二层的嵌套关联的字段名，
        try {
            List<Method> methods = new ArrayList<Method>(args.size());
            BeanInfo beanInfo = Introspector.getBeanInfo(getType());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            PropertyDescriptor[] sonPropertyDescriptors =null;
            for (Map.Entry<String, ? extends Expression<?>> entry : args.entrySet()) {
                String property = entry.getKey();
                String[]  basenames= property.split("\\.");
                Expression<?> expr = entry.getValue();
                Method setter = null;
                for (PropertyDescriptor prop : propertyDescriptors) {
                    String proKey=prop.getName();
                    if(basenames.length>1){
                        //嵌套的关联对象如何 构造器()
                        if (proKey.equals(basenames[0])) {      //同一父辈按顺序列举的；
                            if(!preLinkBase.equals(proKey)) {
                                this.sonSetters[methods.size()]= prop.getWriteMethod();     //第一个出现的2层字段标记：该关联对象构造器；
                                preLinkBase = proKey;       //切换 关联对象，同一个关联对象的多字段安排在一起的；
                                Class<?>  linkPathType= prop.getPropertyType();
                                BeanInfo sonBeanInfo = Introspector.getBeanInfo(linkPathType);
                                sonPropertyDescriptors =sonBeanInfo.getPropertyDescriptors();
                            }
                            for (PropertyDescriptor sonProp : sonPropertyDescriptors) {       //两层嵌套关联的对象：
                                String sonProKey=sonProp.getName();
                                if (sonProKey.equals(basenames[1])) {
                                    setter = sonProp.getWriteMethod();
                                    if (!isAssignableFrom(sonProp.getPropertyType(), expr.getType())) {
                                        typeMismatch(sonProp.getPropertyType(), expr);
                                    }
                                    break;
                                }
                            }
                            if (setter == null) {
                                propertyNotFound(expr, property);
                            }
                            break;
                        }
                    }
                    else {
                        if (proKey.equals(property)) {
                            setter = prop.getWriteMethod();
                            if (!isAssignableFrom(prop.getPropertyType(), expr.getType())) {
                                typeMismatch(prop.getPropertyType(), expr);
                            }
                            break;
                        }
                    }
                }
                if (setter == null) {
                    propertyNotFound(expr, property);
                }
                methods.add(setter);
            }
            return methods;
        } catch (IntrospectionException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    protected void propertyNotFound(Expression<?> expr, String property) {
        // do nothing
    }

    protected void typeMismatch(Class<?> type, Expression<?> expr) {
        final String msg = expr.getType().getName() + " is not compatible with " + type.getName();
        throw new IllegalArgumentException(msg);
    }

    /*这个实际是提取sql执行后的返回数据多个的字段。a[];
     pagedQuery.fetch()后，从hibernate里面getResultList()会调用这个的！
     sql执行结果，然后projection.newInstance(Object[] tuple)就进这里来：a[]：tuple+顺序数据类型；
     要求排序, id,indCod, ,company.id,company.name, , 同一个关联对象的字段必须放在一块；
    * */
    @Override
    public T newInstance(Object... a) {
        try {
            T rv = create(getType());
            if (fieldAccess) {
                for (int i = 0; i < a.length; i++) {
                    Object value = a[i];
                    if (value != null) {
                        Field field = fields.get(i);
                        if (field != null) {
                            field.set(rv, value);
                        }
                    }
                }
            } else {
                Object sonRv =rv;       //第一层字段都要安排在前面的，后面才是嵌套关联对象的第2层字段。
                for (int i = 0; i < a.length; i++) {
                    Object value = a[i];
                    if (value != null) {
                        Method setter = setters.get(i);
                        if (setter != null) {
                            //初始化时，2层字段，顺序上第一个出现的才会设置sonSetters。
                            Method sonSetter = this.sonSetters[i];
                            if(null!=sonSetter) {
                                Class<?> sonClazz = setter.getDeclaringClass();
                                sonRv = create(sonClazz);     //第二层嵌套对象构造器
                                sonSetter.invoke(rv, sonRv);     //只能支持最大两层的嵌套关联
                            }
                            setter.invoke(sonRv, value);
                        }
                    }
                }
            }
            return rv;
        } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
            throw new ExpressionException(e.getMessage(), e);
        }
    }

    protected <T> T create(Class<T> type) throws IllegalAccessException, InstantiationException {
        return type.newInstance();
    }

    /**
     * Create an alias for the expression
     *
     * @return this as alias
     */
    @SuppressWarnings("unchecked")
    public Expression<T> as(Path<T> alias) {
        return ExpressionUtils.operation(getType(),Ops.ALIAS, this, alias);
    }

    /**
     * Create an alias for the expression
     *
     * @return this as alias
     */
    public Expression<T> as(String alias) {
        return as(ExpressionUtils.path(getType(), alias));
    }

    @Override
    public <R,C> R accept(Visitor<R,C> v, C context) {
        return v.visit(this, context);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (obj instanceof QBeanMy<?>) {
            QBeanMy<?> c = (QBeanMy<?>) obj;
            return getArgs().equals(c.getArgs()) && getType().equals(c.getType());
        } else {
            return false;
        }
    }

    @Override
    public List<Expression<?>> getArgs() {
        return new ArrayList<>(bindings.values());
    }
    /**
     * 初始化时 2层关联对象的，默认是cross join,有加了2层对象母字段的是inner join,
     * 而普通的操作要求left outer join=Left join，
     * 需要改成Left join的那些2层嵌套关联字段需要在这绑定注册。
     * */
    public void bindLeftJoin(EntityPath<?>... args) {
        this.leftJoinFields= List.of(args);
    }

    public List<EntityPath<?>> getLeftJoin() {
       return this.leftJoinFields;
    }
}
